#include "EditLayer.h"
#include <imgui.h>
#include <Core/Application.h>
#include <GameObject/Components.h>
#include <Core/RAII.h>
#include <Log/Log.h>
#include <FileSystem/FileSystem.h>
#include <Core/Inputs.h>
#include <EditApp.h>
#include <fstream>
#include <Serialization/Serialization.h>


namespace Edit
{

    std::vector<std::string> componentsList;
    int componentsListInit = ([]() {
        Engine2D::ForeachComponents([](auto comp) {
            Engine2D::Reflect<decltype(comp)> ref;
            if (typeid(decltype(comp)).hash_code() != typeid(Engine2D::NameComponent).hash_code() &&
                typeid(decltype(comp)).hash_code() != typeid(Engine2D::IDComponent).hash_code() &&
                typeid(decltype(comp)).hash_code() != typeid(Engine2D::TagComponent).hash_code()
            ) {
                componentsList.push_back(ref.Name());
            }
        }, Engine2D::AllComponents());
    }(), 0);


    EditLayer::EditLayer()
    {
    }

    void EditLayer::OnAttach(void *)
    {
        m_activeScene = Engine2D::Project::CreateScene();
        Engine2D::ScriptEngine::Instance().SetActiveScene(m_activeScene);
        Engine2D::ScriptEngine::Instance().SetErrorCallback([this](const std::string & str) {
            this->m_log.AddLog(str.c_str());
        });
        // m_windowFlags |= ImGuiWindowFlags_NoMove;
    }

    void EditLayer::OnUpdate(Engine2D::LayerArgs args)
    {
        m_activeScene->Update();
    }

    void EditLayer::OnImGui()
    {
        int window_flags = 0;
        const ImGuiViewport* viewport = ImGui::GetMainViewport();
        ImGui::SetNextWindowPos(viewport->WorkPos);
        ImGui::SetNextWindowSize(viewport->WorkSize);
        ImGui::SetNextWindowViewport(viewport->ID);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
        window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground;
        window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
        Engine2D::RAII raii([] { ImGui::End(); }, [&] { ImGui::Begin("Space", 0, window_flags); ImGui::PopStyleVar(); ImGui::DockSpace(ImGui::GetID("Dock Space")); });
        ImGui::ShowDemoWindow();
        ImGuiMainMenuBar();
        ImGuiObjectAttributeConfig();
        ImGuiControlBar();
        ImGuiSceneMenager();
        ImGuiCenterArea();
        ImGuiDebugArea();
    }

    void EditLayer::ImGuiMainMenuBar()
    {
        static bool lockLayout = false;
        if (ImGui::BeginMainMenuBar()) {
            if (ImGui::BeginMenu("File")) {
                if (ImGui::MenuItem("Save")) {
                    SaveActiveScene();
                }
                ImGui::EndMenu();
            }
            if (ImGui::BeginMenu("Settings")) {
                if (ImGui::MenuItem("Lock Layout", 0, &lockLayout)) {
                    lockLayout ? m_windowFlags |= ImGuiWindowFlags_NoMove : m_windowFlags &= ~ImGuiWindowFlags_NoMove; 
                } 
                ImGui::EndMenu();
            }
            ImGui::EndMainMenuBar();
        }
    }

    void EditLayer::ImGuiControlBar()
    {
        static std::chrono::steady_clock::time_point startTime; 
        Engine2D::RAII raii([] { ImGui::End(); }, [&] { ImGui::Begin("ControlBar", 0, m_windowFlags); });
        if (ImGui::Button("Start")) {
            Engine2D::ScriptComponent::s_doScript = true;
            m_activeScene->Foreach<Engine2D::ScriptComponent>([](std::shared_ptr<Engine2D::IObject> item) {
                item->GetComponent<Engine2D::ScriptComponent>().lock()->Load();
                return Engine2D::IObject::ForeachEnum::Continue;
            });
            startTime = std::chrono::steady_clock::now();
        }
        ImGui::SameLine();
        if (ImGui::Button("Stop")) {
            Engine2D::ScriptComponent::s_doScript = false;
        }
        if (Engine2D::ScriptComponent::s_doScript == true) {
            ImGui::SameLine();
            ImGui::Text("Time: %lldms", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime).count());
        }
    }

    void EditLayer::ImGuiSceneMenager()
    {
        Engine2D::RAII raii([] { ImGui::End(); }, [&] { ImGui::Begin("Scene", 0, m_windowFlags); });
        if (ImGui::BeginPopupContextWindow("RightClickedMenu")) {
            if (ImGui::Selectable("Create Object")) {
                m_activeObject = m_activeScene->CreateBaseObject();
            }
            if (ImGui::Selectable("Create Camera")) {
                m_activeScene->CreateCareraObject();
            }
            ImGui::EndPopup();
        }
        m_activeScene->Foreach([this](std::shared_ptr<Engine2D::IObject> item) {
            if (ImGui::Selectable(item->GetComponent<Engine2D::NameComponent>().lock()->name.c_str(), 
                this->m_activeObject.lock() == item)) {
                this->m_activeObject = item;
            } 
            if (ImGui::BeginPopupContextItem(item->GetComponent<Engine2D::IDComponent>().lock()->uuid().c_str())) {
                if (ImGui::Selectable("Delete")) {
                    Engine2D::Application::Instance<Engine2D::Application>().lock()->GetEventLoop().PushEvent([this, item]() {
                        this->m_activeScene->RemoveObject(item);
                    });
                }
                ImGui::EndPopup();
            }
            return Engine2D::IObject::ForeachEnum::Continue;
        });
    }

    void EditLayer::ImGuiCenterArea()
    {   
        Engine2D::RAII raii([] { ImGui::End(); }, [&] { ImGui::Begin("Game Play", 0, m_windowFlags | ImGuiWindowFlags_NoBackground); });
    }

    void EditLayer::ImGuiDebugArea()
    {
        m_log.Draw("Log");
    }

    void EditLayer::ImGuiObjectAttributeConfig()
    {
        Engine2D::RAII raii([] { ImGui::End(); }, [&] { ImGui::Begin("Attribute", 0, m_windowFlags); });
        auto obj = m_activeObject.lock();
        if (!obj) {
            return;
        }
        ImGui::Text("UUID: %s", obj->GetComponent<Engine2D::IDComponent>().lock()->uuid().c_str());
        if (ImGui::BeginPopupContextItem("UUID")) {
            if (ImGui::Selectable("Copy")) {
                Engine2D::FileSystem::Instance().CopyToClipboard(obj->GetComponent<Engine2D::IDComponent>().lock()->uuid());
            }
            ImGui::EndPopup();
        }
        ImGui::InputText("Name", obj->GetComponent<Engine2D::NameComponent>().lock()->name.data(), 256);
        ImGui::InputTextMultiline("Tag", obj->GetComponent<Engine2D::TagComponent>().lock()->tag.data(), 1024);
        ImGui::Separator(); 

        static std::string currentComponent = *componentsList.begin();
        if (ImGui::BeginCombo("Components", currentComponent.c_str())) {
            for (auto && item : componentsList) {
                if (ImGui::Selectable(item.c_str())) {
                    currentComponent = item;
                }
            }
            ImGui::EndCombo();
        }

        ImGui::SameLine();
        if (ImGui::Button("Add")) {
            Engine2D::ForeachComponents([&obj](auto comp) {
                Engine2D::Reflect<decltype(comp)> ref;
                if (currentComponent == ref.Name()) {
                    obj->AddComponent<decltype(comp)>();
                }
            }, Engine2D::AllComponents());
        }

        obj->ForeachComponents([this, obj](auto & comp) {
            if(!ImGuiShowComponent(comp)) {
                obj->RemoveComponent<std::decay_t<decltype(comp)>>();
            }
        }, Engine2D::AllComponents());
    }

    void EditLayer::SaveActiveScene()
    {
        Engine2D::Project::SaveProject(m_activeScene);
    }


    template<>
    bool EditLayer::ImGuiShowComponent<Engine2D::TransformComponent>(Engine2D::TransformComponent & comp) {
        bool closeFlag = true;
        if (ImGui::CollapsingHeader("Transform", &closeFlag)) {
            ImGui::DragFloat2("position", 
                &comp.translate.x,
                1.0f, -9999.0f, 9999.0f
            );
            ImGui::DragFloat2("scale", 
                &comp.scale.x,
                0.2f, -99.0f, 99.0f
            );
            ImGui::DragFloat("rotate", 
                &comp.rotate,
                1.0f, -180.0f, 180.0f
            );
        }
        return closeFlag;
    }

    template<>
    bool EditLayer::ImGuiShowComponent<Engine2D::RenderRectComponent>(Engine2D::RenderRectComponent & comp) {
        bool closeFlag = true;
        if (ImGui::CollapsingHeader("RenderRectComponent", &closeFlag)) {
            ImVec4 color(comp.color.x, comp.color.y, comp.color.z, comp.color.w);
            if (ImGui::ColorEdit4("Color", &color.x)) {
                comp.color.x = color.x;
                comp.color.y = color.y;
                comp.color.z = color.z;
                comp.color.w = color.w;
            }

        }
        return closeFlag;
    }

    template<>
    bool EditLayer::ImGuiShowComponent<Engine2D::ScriptComponent>(Engine2D::ScriptComponent & comp) {
        bool closeFlag = true;
        if (ImGui::CollapsingHeader("ScriptComponent", &closeFlag)) {
            if (ImGui::Button("Open")) {
                comp.fileName = Engine2D::FileSystem::Instance().GetFileName(Engine2D::FileSystem::Instance().GetCWD(), "lua\0*.lua\0\0");
            }
            ImGui::SameLine();
            ImGui::InputText("File", comp.fileName.data(), 256, ImGuiInputTextFlags_ReadOnly);
        }
        return closeFlag;
    }
    
    template<>
    bool EditLayer::ImGuiShowComponent<Engine2D::CameraComponent>(Engine2D::CameraComponent & comp) {
        bool closeFlag = true;
        if (ImGui::CollapsingHeader("CameraComponent", &closeFlag)) {
            ImGui::Checkbox("Set this camera as main camera", &comp.isMainCamera);
        }   
        return closeFlag;
    }

    template<>
    bool EditLayer::ImGuiShowComponent<Engine2D::RenderTextureComponent>(Engine2D::RenderTextureComponent & comp) {
        bool closeFlag = true;
        if (ImGui::CollapsingHeader("RenderTextureComponent", &closeFlag)) {
            if (ImGui::Button("Load")) {
                comp.fileName = Engine2D::FileSystem::Instance().GetFileName(Engine2D::FileSystem::Instance().GetCWD(), "*.*\0*.*\0\0");
                comp.Load();
            }
            ImGui::SameLine();
            ImGui::InputText("File", comp.fileName.data(), 256, ImGuiInputTextFlags_ReadOnly);
            ImGui::DragFloat2("Src Size", &comp.srcSize.x);
            ImGui::DragFloat2("Dst Size", &comp.dstSize.x);
        }   
        return closeFlag;
    }

} // namespace Edit
